/**
 * 核心层接口
 *
 * Created by snowingsea on 2020/03/01.
 */
const thrift = require('./thrift-client');
const tests = require('./../tests');
const funcs = require('../functions');
const projectVersions = require('../project-versions');
const utility = require('../utility');
const testcaseGroup = require('../test-case-groups');
const fileModel = require('../files');
const invokeRelation = require('../invoke-relations');
const gitServer = require('../git/git-sever');
const gitClient = require('../git/git-client');
const breakpoint = require('../break-points');
const logger = require('../logger');

/**
 * 获取机器码
 * @param {Boolean} short 短码
 * @return {Promise}
 */
function getMachineCode(short = false) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.machine_code(short, (err, msg) => {
          err ? reject(err) : resolve(msg);
        });
      })
      .catch(reject);
  });
}

/**
 * 注册许可证
 * @param {String} license 许可证字符串
 * @return {Promise}
 */
function registerLicense(license) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.license_register(license, (err, msg) => {
          err ? reject(err) : resolve(msg);
        });
      })
      .catch(reject);
  });
}

/**
 * 单元测试解析源码
 * @param {Array} files 文件列表
 * @param {Object} project 项目信息
 * @param {Object} projectVersion 项目版本信息
 * @param {Object} task 任务信息
 * @return {Promise}
 */
function parse(files, project, projectVersion, task) {
  if (files.length === 0) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          projectId,
          currentCommitId,
          includes,
          macros,
          caseSensitive,
          systemType,
          masterVersionId,
        } = projectVersion;
        client.parse(
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          files.map(({ path }) => path),
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          systemType,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          } // eslint-disable-line comma-dangle
        );
      })
      .catch(reject);
  });
}
/**
 * 集成测试解析源码
 * @param {Array} files 文件列表
 * @param {Object} project 项目信息
 * @param {Object} projectVersion 项目版本信息
 * @param {Object} task 任务信息
 * @return {Promise}
 */
function integrationParse(files, project, projectVersion, task) {
  if (files.length === 0) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          projectId,
          currentCommitId,
          includes,
          macros,
          caseSensitive,
          systemType,
          masterVersionId,
        } = projectVersion;
        client.integration_parse(
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          files.map(({ path }) => path),
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          systemType,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          } // eslint-disable-line comma-dangle
        );
      })
      .catch(reject);
  });
}

async function instrument(fileFuncs, project, projectVersion, task) {
  if (fileFuncs.length === 0) {
    return null;
  }
  const client = await thrift.createClient();
  const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
  const {
    projectId,
    currentCommitId,
    includes,
    macros,
    masterVersionId,
    caseSensitive,
    onlyReplaceStubFunction,
  } = projectVersion;
  return new Promise((resolve, reject) => {
    client.instrument(
      task._id.toString(),
      projectId.toString(),
      masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
      projectURL,
      currentCommitId,
      fileFuncs.reduce((obj, ff) => {
        obj[ff.filePath] = ff.functions.map((f) => (funcs.isCFunction(f) ? f.functionName : f.mangledId));
        return obj;
      }, {}),
      includes,
      macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
      !caseSensitive,
      onlyReplaceStubFunction,
      // systemType,
      (err, msg) => {
        err ? reject(err) : resolve(msg);
      } // eslint-disable-line comma-dangle
    );
  });
}
/**
 * 模型检查/代码证明/缺陷扫描
 * @param {Array} files 文件列表
 * @param {Array} entriesFuncs 入口函数
 * @param {Object} project 项目信息
 * @param {Object} projectVersion 项目版本信息
 * @param {Object} task 任务信息
 * @return {Promise}
 */
function prove(files, entriesFuncs, project, projectVersion, task) {
  if (files.length === 0 || entriesFuncs.length === 0) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          projectId,
          currentCommitId,
          includes,
          macros,
          caseSensitive,
          masterVersionId,
        } = projectVersion;
        client.prove(
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          entriesFuncs,
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          } // eslint-disable-line comma-dangle
        );
      })
      .catch(reject);
  });
}

/**
 * 生成测试用例
 * @param {Array} functions 函数信息列表
 * @param {Map} fileMap 文件集合
 * @param {Object} project 项目信息
 * @param {Object} projectVersion 项目版本信息
 * @param {Object} task 任务信息
 * @return {Promise}
 */
async function generateTests(functions, fileMap, project, projectVersion, task) {
  if (functions.length === 0) {
    return Promise.resolve();
  }
  // 测试用例生成依据信息集合
  const infos = {};
  const sources = [];
  const relatedFiles = {};
  if (projectVersion.versionType === projectVersions.versionType.integrationTest) {
    // 参与集成函数打桩和集成文件信息
    await tests.integrationTestGenerateTestStubsAndFiles(functions, projectVersion);
  }
  await utility.arrayChunkOperation(functions, 50, async (func) => {
    const borderVariables = await funcs.getFuncBorderVariable(func._id);
    const key = `${fileMap.get(func.fileId.toString()).path}:${funcs.isCFunction(func) ? func.functionName : func.mangledId}`;
    sources.push(key);
    if (borderVariables) {
      func.borders = { borders: {} };
      ['params', 'global'].forEach((key) => {
        if (borderVariables[key]) {
          Object.keys(borderVariables[key]).forEach((variableName) => {
            const borderInfo = [];
            const { range } = borderVariables[key][variableName];
            if (range) {
              borderInfo.push(`${range.left}:${range.right}:${range.mode}`);
            }
            func.borders.borders[variableName] = borderInfo;
          });
        }
      });
      if (Object.keys(func.borders.borders).length > 0) {
        infos[key] = func.borders;
      }
    }
    if (projectVersion.versionType === projectVersions.versionType.integrationTest) {
      infos[key] = infos[key] || {};
      infos[key].stubs = func.stubs;
      relatedFiles[key] = func.files;
    }
    if (infos[key]) {
      infos[key] = JSON.stringify(infos[key]);
    }
  });
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          projectId,
          currentCommitId,
          includes,
          macros,
          coverages,
          caseSensitive,
          systemType,
          timeoutTestGen,
          masterVersionId,
        } = projectVersion;
        // unit test generate testcases
        if (projectVersion.versionType === projectVersions.versionType.unitTest) {
          client.testcase_gen(
            task._id.toString(),
            projectId.toString(),
            masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
            projectURL,
            currentCommitId,
            sources,
            includes,
            macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
            coverages,
            !caseSensitive,
            systemType,
            infos,
            timeoutTestGen || 10,
            (err, msg) => {
              err ? reject(err) : resolve(msg);
            }
          );
        } else {
          // integration test generate testcases
          client.integration_test_gen(
            task._id.toString(),
            projectId.toString(),
            masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
            projectURL,
            currentCommitId,
            sources,
            includes,
            macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
            coverages,
            !caseSensitive,
            systemType,
            relatedFiles,
            infos,
            timeoutTestGen || 10,
            (err, msg) => {
              err ? reject(err) : resolve(msg);
            }
          );
        }
      })
      .catch(reject);
  });
}

function groupTestCasesByFunction(testCases) {
  const fnMap = {};
  testCases.forEach((item) => {
    if (fnMap[item.functionId]) {
      fnMap[item.functionId].push(item);
    } else {
      fnMap[item.functionId] = [item];
    }
  });
  return Object.entries(fnMap);
}

async function prepareTestCaseToRun(testCases, groupIds) {
  const trees = Array.prototype.concat(
    ...await Promise.all(groupTestCasesByFunction(testCases).map(
      ([functionId, testCases]) => {
        let thirftArgsCommon;
        let entranceCommon;
        let specsCommon;
        // eslint-disable-next-line
        const fixedTestCases = testCases.map(({ _id, kind, thriftArgs, specs, entrance, data }) => {
          if (thriftArgs) thirftArgsCommon = thriftArgs;
          if (entrance) entranceCommon = entrance;
          if (specs) specsCommon = specs;
          // TODO: if the `kind` is not being used, remove it.
          // eslint-disable-next-line
          return Object.assign(data, { _id, kind, entrance, specs });
        });
        return testcaseGroup.buildTreeWithFunctionIdAndTestCases(functionId, fixedTestCases, groupIds)
          .then((trees) => trees.map((tree) => ({
            ...thirftArgsCommon,
            no: tree.testcase.toString(),
            data: JSON.stringify(Object.assign(tree, {
              entrance: entranceCommon,
              specs: specsCommon,
            })),
          })));
      }
    ))
  );
  return trees.map((t) => new thrift.types.Testcase(t));
}

function runTestsCommon(testCases, fileMap, functionMap, project, projectVersion, task, handler, groupIds) {
  if (testCases.length === 0) {
    return Promise.resolve();
  }
  return thrift.createClient()
    .then((client) => {
      const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
      const {
        projectId,
        currentCommitId,
        includes,
        macros,
        caseSensitive,
        systemType,
        timeoutTestRun,
        masterVersionId,
        tolerance,
        autoFill = false,
      } = projectVersion;

      return tests.getTestFieldConfig([...functionMap.keys()], projectVersion, ['functionId', 'numericalBases'])
        .then((numericalBasesConfigs) => {
          const numericalBasesConfigMap = new Map();
          numericalBasesConfigs.forEach((numericalBasesConfig) => {
            numericalBasesConfigMap.set(numericalBasesConfig.functionId.toString(), numericalBasesConfig);
          });
          return utility.arrayChunkOperation(testCases, 100, (item) => {
            const functionId = item.functionId.toString();
            const func = functionMap.get(functionId);
            const {
              mangledId,
              variables,
              specs,
              sourceParsedFile,
            } = func;
            let { fileId } = func;
            if (sourceParsedFile.length > 0) {
              [fileId] = sourceParsedFile;
            }
            if (!item.data) {
              item.data = {
                testcase: item._id,
              };
            }
            tests.removeResult(item.data);
            const numericalBases = numericalBasesConfigMap.has(functionId) ? numericalBasesConfigMap.get(functionId).numericalBases : null;
            tests.completeTest(
              item,
              variables,
              fileMap.get(fileId.toString()).fileTypeSystem,
              { tolerance, numericalBases, autoFill }
            );

            /*
          if (specs && specs.length > 0) {
            item.data.specs = specs;
          }
          */
            item.specs = specs;
            item.thriftArgs = {
              file: fileMap.get(fileId.toString()).path,
              func: mangledId,
              no: '', // to be filled with testCaseId or testCaseGroupId (item._id.toString())
              data: '{}', // to be filled with JSON string of item.data
            // kind: item.kind, // TODO: make sure that the `kind` field is not being used
            };
            return (async () => {
            // breakpoint数据
              const breakpoints = await breakpoint.getBreakPointsByTestcase(item._id,
                {
                  functionVariable: variables,
                  types: fileMap.get(fileId.toString()).fileTypeSystem,
                });
              if (breakpoints.length) {
                item.data.runtime = {};
                breakpoints.forEach((bp) => {
                  const { expandedVariables = {}, line, times } = bp;
                  expandedVariables['@shouldExit'] = bp.exit;
                  // local key特殊处理
                  const { local } = expandedVariables;
                  if (local) {
                    logger.verbose(JSON.stringify(local, null, 1));
                    expandedVariables.local = Object.keys(local).reduce((o, localKey) => {
                      const item = local[localKey];
                      o[item['@name']] = item;
                      return o;
                    }, {});
                  }
                  if (bp.code) {
                    expandedVariables.code = bp.code;
                  }
                  const obj = { runtime: expandedVariables };
                  tests.removeResult(obj); // 清除结果字段
                  // 核心times从0开始
                  utility.setValueByKeyPath(item.data.runtime, `${line}.${times - 1}`, obj.runtime);
                });
              }

              return item;
            })();
          });
        })
        .then((item) => prepareTestCaseToRun(item, groupIds))
        .then((testcases) => handler(
          client,
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          testcases,
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          systemType,
          timeoutTestRun || 25,
        ));
    });
}

async function runIntegrationTestsCommon(
  testCases, fileMap, functionMap, project, projectVersion, task, handler, groupIds
) {
  if (testCases.length === 0) {
    return Promise.resolve();
  }
  const client = await thrift.createClient();
  const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
  const {
    _id: versionId,
    projectId,
    currentCommitId,
    includes,
    macros,
    caseSensitive,
    systemType,
    timeoutTestRun,
    masterVersionId,
    tolerance,
  } = projectVersion;
  const testCaseFilesMap = {};
  // 获取当前版本所有函数
  const allFuncMap = await funcs.getFunctionByVersionId(versionId, {
    _id: 1, fileId: 1, functionName: 1, functionAttrs: 1, sourceParsedFile: 1, kind: 1, mangledId: 1, language: 1,
  });
  const allFileMap = await fileModel.getSourceFileByVersionId(
    versionId,
    null,
    {
      _id: 1,
      path: 1,
      status: 1,
      kind: 1,
    }
  );
  const allFuncVariablesMap = new Map();
  const allFileTypeSystemMap = new Map();
  const functionIds = [];
  const fileIds = [];
  const allRelationFuncsMap = new Map();
  functionMap.forEach((value, key) => {
    functionIds.push(key);
    fileIds.push(value.fileId);
  });
  await utility.arrayChunkOperation(functionIds, 100, async (id) => {
    const relationFuncs = await invokeRelation.retrieveRelations(id, { initRelations: true });
    allRelationFuncsMap.set(id.toString(), relationFuncs);
  });
  const allFileTypes = await fileModel.getFileTypeSystemByVersion(versionId);
  allFileTypes.forEach((fileTypes) => {
    allFileTypeSystemMap.set(fileTypes.fileId.toString(), fileTypes.types);
  });
  const allFileGlobals = await funcs.getVersionGlobalVariables(versionId, 'isDefined');
  const allFuncVariableStubs = await funcs.getFunctionStubsByVersionId(versionId);
  const allFuncVariables = await utility.arrayChunkOperation(
    functionIds,
    80,
    (funcId) => funcs.getRelationFuncsVariables(funcId, {
      includeNonStub: true,
      allFileMap,
      allFuncVariableStubs,
      allFuncMap,
      includeSpecializationFunc: true,
      allFileGlobals,
    })
  );
  allFuncVariables.forEach((variable) => {
    allFuncVariablesMap.set(variable.functionId.toString(), variable);
  });
  const originalFunctionVariablesMap = new Map();
  const numericalBasesConfigs = await tests.getTestFieldConfig(functionIds, projectVersion, ['functionId', 'numericalBases']);
  const numericalBasesConfigMap = new Map();
  numericalBasesConfigs.forEach((numericalBasesConfig) => {
    numericalBasesConfigMap.set(numericalBasesConfig.functionId.toString(), numericalBasesConfig);
  });
  const testcasesFlatawait = await utility.arrayChunkOperation(testCases, 100, async (item) => {
    const { functionId } = item;
    delete item.fileTypeSystem;
    const {
      fileId,
      mangledId,
      functionName,
      specs,
      language,
    } = functionMap.get(functionId.toString());
    if (!item.data) {
      item.data = {
        testcase: item._id,
      };
    }
    // breakpoint数据
    const breakpoints = await breakpoint.getBreakPointsByTestcase(item._id,
      {
        fileTypesMap: allFileTypeSystemMap,
        functionVariablesMap: originalFunctionVariablesMap,
      });
    if (breakpoints.length > 0) {
      item.data.runtime = {};
      breakpoints.forEach((bp) => {
        const {
          expandedVariables, line, times, fileId,
        } = bp;
        expandedVariables['@shouldExit'] = bp.exit;
        // local key特殊处理
        const { local } = expandedVariables;
        if (local) {
          logger.verbose(JSON.stringify(local, null, 1));
          expandedVariables.local = Object.keys(local).reduce((o, localKey) => {
            const item = local[localKey];
            o[item['@name']] = item;
            return o;
          }, {});
        }
        const obj = { runtime: expandedVariables };
        // 核心times从0开始
        utility.setValueByKeyPathArray(item.data.runtime, [`${line}@${allFileMap.get(fileId.toString()).path}`, `${times - 1}`], obj.runtime);
      });
    }
    tests.removeResult(item.data);
    const variables = allFuncVariablesMap.get(functionId.toString());
    const { types: fileTypeSystem } = await tests.integrationGetFilesTypeSystemByTestId({
      testCase: item,
      allFileTypeMap: allFileTypeSystemMap,
      allFuncMap,
      allRelationFuncsMap,
      funcVariables: variables,
      allFuncVariableStubs,
      allFileMap,
      allFileGlobals,
    });
    const numericalBases = numericalBasesConfigMap.has(functionId) ? numericalBasesConfigMap.get(functionId).numericalBases : null;
    tests.completeTest(
      item,
      variables,
      fileTypeSystem,
      {
        integration: true, allFuncMap, tolerance, language, numericalBases,
      },
    );
    item.specs = specs;
    let entrance;
    switch (language) {
      case 'c':
        entrance = `${functionName}@${fileMap.get(fileId.toString()).path}`;
        break;
      case 'c++':
        entrance = `${mangledId}@${fileMap.get(fileId.toString()).path}`;
        break;
      default:
        throw new Error('language should be c or c++');
    }
    item.entrance = entrance;
    item.data.__files.add(allFileMap.get(item.fileId.toString()).path);
    // 处理文件列表
    tests.handleTestCaseFiles(
      item,
      allFileMap,
      allFuncMap
    );
    testCaseFilesMap[item._id.toString()] = [...item.data.__files];
    delete item.data.__files;
    item.thriftArgs = {
      file: fileMap.get(fileId.toString()).path,
      func: mangledId,
      no: '', // to be filled with testCaseId or testCaseGroupId (item._id.toString())
      data: '{}', // to be filled with JSON string of item.data
    };
    return item;
  });
  const testcases = await prepareTestCaseToRun(testcasesFlatawait, groupIds);
  return handler(
    client,
    task._id.toString(),
    projectId.toString(),
    masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
    projectURL,
    currentCommitId,
    testcases,
    includes,
    macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
    !caseSensitive,
    systemType,
    timeoutTestRun || 25,
    testCaseFilesMap,
  );
}

/**
 * 执行测试用例
 * @param {Array} testCases 测试用例列表
 * @param {Map} fileMap 文件集合
 * @param {Map} functionMap 函数集合
 * @param {Object} project 项目
 * @param {Object} projectVersion 项目版本
 * @param {Boolean} withoutCoverage 是否收集覆盖率
 * @return {Promise}
 */
function runTests(testCases, fileMap, functionMap, project, projectVersion, task, withoutCoverage, groupIds) {
  const handler = (
    client, testerId, projectId, versionId, projectURL, commitId, testcases, headers, macros,
    ignorecase, mode, timeout
  ) => new Promise((resolve, reject) => client.testcase_run(
    testerId,
    projectId,
    versionId,
    projectURL,
    commitId,
    testcases,
    headers,
    macros,
    ignorecase,
    mode,
    timeout,
    withoutCoverage,
    (err, msg) => {
      err ? reject(err) : resolve(msg);
    }
  ));
  return runTestsCommon(testCases, fileMap, functionMap, project, projectVersion, task, handler, groupIds);
}

/**
 * 执行集成测试用例
 * @param {Array} testCases 测试用例列表
 * @param {Map} fileMap 文件集合
 * @param {Map} functionMap 函数集合
 * @param {Object} project 项目
 * @param {Object} projectVersion 项目版本
 * @param {Object} task 任务信息
 * @param {Boolean} withoutCoverage 是否收集覆盖率
 * @return {Promise}
 */
function runIntegrationTests(
  testCases,
  fileMap,
  functionMap,
  project,
  projectVersion,
  task,
  withoutCoverage,
  groupIds
) {
  const handler = (
    client, testerId, projectId, versionId, projectURL, commitId, testcases, headers, macros,
    ignorecase, mode, timeout, files,
  ) => new Promise((resolve, reject) => client.integration_test_run(
    testerId,
    projectId,
    versionId,
    projectURL,
    commitId,
    testcases,
    headers,
    macros,
    ignorecase,
    mode,
    timeout,
    files,
    withoutCoverage,
    (err, msg) => {
      err ? reject(err) : resolve(msg);
    }
  ));
  return runIntegrationTestsCommon(
    testCases, fileMap, functionMap, project, projectVersion, task, handler, groupIds
  );
}

/**
 * 导出目标机测试用例
 * @param {Array} testCases 测试用例列表
 * @param {Map} fileMap 文件集合
 * @param {Map} functionMap 函数集合
 * @param {Object} project 项目
 * @param {Object} projectVersion 项目版本
 * @param {Boolean} withoutCoverage 是否收集覆盖率
 * @return {Promise}
 */
function exportTargetMachineTests(
  testCases, fileMap, functionMap, project, projectVersion, task, withoutCoverage
) {
  const handler = (
    client, testerId, projectId, versionId, projectURL, commitId, testcases, headers, macros,
    ignorecase, mode, timeout
  ) => new Promise((resolve, reject) => client.testcase_export(
    testerId,
    projectId,
    versionId,
    projectURL,
    commitId,
    testcases,
    headers,
    macros,
    ignorecase,
    mode,
    timeout,
    withoutCoverage,
    (err, msg) => {
      err ? reject(err) : resolve(msg);
    }
  ));
  return runTestsCommon(testCases, fileMap, functionMap, project, projectVersion, task, handler);
}

/**
 * 导出目标机集成测试用例
 * @param {Array} testCases 测试用例列表
 * @param {Map} fileMap 文件集合
 * @param {Map} functionMap 函数集合
 * @param {Object} project 项目
 * @param {Object} projectVersion 项目版本
 * @param {Object} projectVersion 项目版本
 * @param {Boolean} withoutCoverage 是否收集覆盖率
 * @return {Promise}
 */
async function exportTargetMachineIntegrationTests(
  testCases, fileMap, functionMap, project, projectVersion, task, withoutCoverage
) {
  const handler = (
    client, testerId, projectId, versionId, projectURL, commitId, testcases, headers, macros,
    ignorecase, mode, timeout, files,
  ) => new Promise((resolve, reject) => client.integration_testcase_export(
    testerId,
    projectId,
    versionId,
    projectURL,
    commitId,
    testcases,
    headers,
    macros,
    ignorecase,
    mode,
    timeout,
    files,
    withoutCoverage,
    (err, msg) => {
      err ? reject(err) : resolve(msg);
    }
  ));
  return runIntegrationTestsCommon(
    testCases, fileMap, functionMap, project, projectVersion, task, handler
  );
}

/**
 * 暂停任务
 * @param {String} taskId 任务id
 * @return {Promise}
 */
function suspendTask(taskId) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.task_suspend(taskId, (err) => {
          err ? reject(err) : resolve();
        });
      })
      .catch(reject);
  });
}

/**
 * 继续任务
 * @param {String} taskId 任务id
 * @return {Promise}
 */
function resumeTask(taskId) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.task_resume(taskId, (err) => {
          err ? reject(err) : resolve();
        });
      })
      .catch(reject);
  });
}

/**
 * 取消任务
 * @param {String} taskId 任务id
 * @return {Promise}
 */
function cancelTask(taskId) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.task_cancel(taskId, (err) => {
          err ? reject(err) : resolve();
        });
      })
      .catch(reject);
  });
}

/**
 * 获取编译配置选项
 * @return {Promise<string>}
 */
function getCompilationOptions() {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.compilation_options_query((err, msg) => {
          err ? reject(err) : resolve(msg);
        });
      })
      .catch(reject);
  });
}

/**
 * 特化
 * @param {Array} specialData 特化数据
 * @param {Object} file 文件
 * @param {Object} project 项目
 * @param {Object} projectVersion 项目版本
 * @return {Promise<string>}
 */
function specialize(specialData, file, project, projectVersion) {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          creatorId,
          projectId,
          currentCommitId,
          includes,
          macros,
          caseSensitive,
          systemType,
          masterVersionId,
        } = projectVersion;
        client.specialize(
          creatorId.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          new thrift.types.SpecAction({ file: file.path, data: JSON.stringify(specialData) }),
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          systemType,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          }
        );
      })
      .catch(reject);
  });
}

/**
 * 静态分析
 * @param {Array} files 文件列表
 * @param {Object} project 项目信息
 * @param {Object} projectVersion 项目版本信息
 * @param {Object} task 任务信息
 * @return {Promise}
 */
function analyze(files, project, projectVersion, task) {
  if (files.length === 0) {
    return Promise.resolve();
  }
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        const projectURL = gitServer.getRemoteURL({ _id: project.creatorId }, project);
        const {
          projectId,
          currentCommitId,
          includes,
          macros,
          caseSensitive,
          systemType,
          masterVersionId,
          specs,
        } = projectVersion;
        client.analyze(
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          projectURL,
          currentCommitId,
          files.map(({ path }) => path),
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          !caseSensitive,
          systemType,
          specs,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          } // eslint-disable-line comma-dangle
        );
      })
      .catch(reject);
  });
}

function analyzeCodeInstrumentationResult(codeInstrumentationId, statisticalFuncs, resultFiles, project, projectVersion, task) {
  return new Promise((resolve, reject) => {
    let lastCommitId;
    gitClient.getLatestCommitId({ _id: project.creatorId }, project, { _id: codeInstrumentationId })
      .then((commitId) => {
        lastCommitId = commitId;
        return thrift.createClient();
      })
      .then((client) => {
        const {
          projectId,
          includes,
          macros,
          caseSensitive,
          masterVersionId,
        } = projectVersion;
        client.coverage(
          task._id.toString(),
          projectId.toString(),
          masterVersionId ? masterVersionId.toString() : projectVersion._id.toString(),
          gitServer.getRemoteURL({ _id: project.creatorId }, project),
          lastCommitId,
          statisticalFuncs,
          resultFiles,
          includes,
          macros.map(({ key, value }) => new thrift.types.Macro({ name: key, value })),
          caseSensitive,
          (err, msg) => {
            err ? reject(err) : resolve(msg);
          } // eslint-disable-line comma-dangle
        );
      })
      .catch(reject);
  });
}

/**
 * 获取编译配置选项
 * @return {Promise<string>}
 */
function getLicenseStatus() {
  return new Promise((resolve, reject) => {
    thrift.createClient()
      .then((client) => {
        client.license_query((err, msg) => {
          err ? reject(err) : resolve(msg);
        });
      })
      .catch(reject);
  });
}

/**
 * Export model definition object.
 */
module.exports = {
  types: thrift.types,

  getMachineCode,
  registerLicense,

  parse,
  integrationParse,
  prove,
  analyze,
  analyzeCodeInstrumentationResult,
  generateTests,

  runTests,
  runIntegrationTests,

  instrument,

  exportTargetMachineTests,
  exportTargetMachineIntegrationTests,

  suspendTask,
  resumeTask,
  cancelTask,

  specialize,

  getCompilationOptions,

  getLicenseStatus,
};
